//
//  LiveFader.swift
//  LiveFader
//
//  Created by cem.olcay on 11/02/2019.
//  Copyright © 2019 cemolcay. All rights reserved.
//

import UIKit

/// A UIControl subclass for creating customisable horizontal or vertical faders.
open class LiveFaderView: UIControl {
    /// Fader's control direction of the fader.
    public enum ControlDirection: Int, Codable {
        /// Horizontal fader.
        case horizontal
        /// Vertical fader.
        case vertical
    }
    
    /// Fader's contol style.
    public enum ControlStyle: Int, Codable {
        /// Starts from bottom on vertical faders, starts from left end on horizontal faders.
        case fromBottom
        /// Starts from middle.
        case fromMiddle
    }
    
    /// 控件类型
    public enum ControlType: Int, Codable {
        
        case normal
        case thumb
        
    }
    
    /// Whether changes in the value of the knob generate continuous update events. Defaults true.
    public var continuous = false
    
    /// Control style of the fader. Defaults `fromMiddle`.
    public var style = ControlStyle.fromMiddle { didSet{ setNeedsLayout() }}
    /// Control type of the fader. Defaults vertical.
    public var direction = ControlDirection.vertical { didSet { setNeedsLayout() }}
    
    public var type = ControlType.normal {
        didSet {
            if type == .thumb {
                thumbImageView.isHidden = false
            } else {
                thumbImageView.isHidden = true
            }
            
            setNeedsLayout()
        }
    }
    
    /// Current value of the fader. Defaults 0.
    public var value: Double = 0 { didSet{ setNeedsLayout() }}
    /// Maximum value of the fader. Defaults 0.
    public var minValue: Double = 0 { didSet{ setNeedsLayout() }}
    /// Minimum value of the fader. Defaults 1.
    public var maxValue: Double = 1 { didSet{ setNeedsLayout() }}
    
    private var midValue: Double {
        (maxValue + minValue) / 2
    }
    
    /// Enabled background color.
    public var faderEnabledBackgroundColor: UIColor = .lightGray { didSet{ setupColors() }}
    /// Enabled foreground color.
    public var faderEnabledForegroundColor: UIColor = .blue { didSet{ setupColors() }}
    
    /// Highlighted background color. If not set, enabled bacground color will be used.
    public var faderHighlightedBackgroundColor: UIColor? { didSet{ setupColors() }}
    /// Highlighted foreground color. If not set, enabled foreground color will be used.
    public var faderHighlightedForegroundColor: UIColor? { didSet{ setupColors() }}
    
    /// Disabled background color.
    public var faderDisabledBackgroundColor: UIColor? = .lightGray { didSet{ setupColors() }}
    /// Disabled foreground color.
    public var faderDisabledForegroundColor: UIColor? = .blue { didSet{ setupColors() }}
    
    /// bar 的直径比例
    public var trackRate: Double = 1 {
        didSet {
            if trackRate > 1 || trackRate < 0 {
                trackRate = 1
            }
            
            setNeedsLayout()
        }
    }
    public var trackCornerRadius: Double = 0
    
    public var thumbExtraSize: Double = 4
    
    open override var isEnabled: Bool { didSet { setupColors() }}
    open override var isHighlighted: Bool { didSet { setupColors() }}
    
    /// 背景条
    public private(set) lazy var trackBgLayer = CALayer()
    
    /// Foreground layer rendering the `value` of the fader.
    public var faderLayer = CALayer()
    
    public private(set) var thumbImageView = UIImageView()
    
    /// Pan gesture setting the `value`.
    public lazy var panGestureRecognizer = UIPanGestureRecognizer()
    public lazy var tapGestureRecognizer = UITapGestureRecognizer()
    
    // MARK: Init
    
    public override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }
    
    /// Initializes the fader view.
    public func commonInit() {
        backgroundColor = .clear
        
        layer.addSublayer(trackBgLayer)
        
        layer.addSublayer(faderLayer)
        
        addSubview(thumbImageView)
        
        thumbImageView.isHidden = true
        thumbImageView.backgroundColor = .red
        
        // Setup gesture recognizers.
        panGestureRecognizer.addTarget(self, action: #selector(handleGestureRecognizer(gestureRecognizer:)))
        addGestureRecognizer(panGestureRecognizer)
        
        tapGestureRecognizer.addTarget(self, action: #selector(handleGestureRecognizer(gestureRecognizer:)))
        addGestureRecognizer(tapGestureRecognizer)
    }
    
    // MARK: Lifecyle
    
    open override func layoutSubviews() {
        super.layoutSubviews()
        
        if value < minValue || value > maxValue {
            return
        }
        
        var tSize: Double = 0
        if direction == .vertical {
            let width = frame.size.width * trackRate
            tSize = width
            
            trackBgLayer.frame = CGRect(x: (frame.size.width - width) / 2, y: 0, width: width, height: frame.size.height)
        } else {
            let height = frame.size.height * trackRate
            tSize = height
            
            trackBgLayer.frame = CGRect(x: 0, y: (frame.size.height - height) / 2, width: frame.size.width, height: height)
        }
        
        if trackCornerRadius == 0 {
            trackCornerRadius = tSize / 2
        }
        trackBgLayer.cornerRadius = trackCornerRadius
        trackBgLayer.masksToBounds = true
        
        faderLayer.cornerRadius = trackCornerRadius
        faderLayer.masksToBounds = true
        
        if type == .thumb {
            thumbImageView.layer.cornerRadius = (tSize + thumbExtraSize) / 2
            thumbImageView.layer.masksToBounds = true
        }
        
        var tx = Double(0)
        var ty = Double(0)
        var tw: Double = 0
        var th: Double = 0
        
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        
        switch (style, direction) {
        case (.fromBottom, .vertical):
//            let toRange = 0...Double(frame.size.height)
            let toRange = getSlideFrameRange()
            var height = CGFloat(convert(
                value: value,
                inRange: minValue...maxValue,
                toRange: toRange))
            height = height.isNaN ? 0 : height
            
            let width = frame.size.width * trackRate
            
            //            print("----- width = \(width), height = \(height)")
            
            tx = (frame.size.width - width) / 2
            ty = frame.size.height - height
            tw = width
            th = height
            faderLayer.frame = CGRect(x: tx, y: ty, width: tw, height: th)
            
            if type != .thumb {
                break
            }
            
            tw = width + thumbExtraSize
            
            ty = height - tw / 2
            if ty < 0 {
                ty = 0
            } else if ty > frame.size.height - tw {
                ty = frame.size.height - tw
            }
            
            thumbImageView.frame = CGRect(x: tx, y: ty, width: tw, height: tw)
            
        case (.fromBottom, .horizontal):
//            let toRange = 0...Double(frame.size.width)
            let toRange = getSlideFrameRange()
            var width = CGFloat(convert(
                value: value,
                inRange: minValue...maxValue,
                toRange: toRange))
            width = width.isNaN ? 0 : width
            
            let height = frame.size.height * trackRate
            
            //            print("----- width = \(width), height = \(height)")
            
            tx = 0
            ty = (frame.size.height - height) / 2
            tw = width
            th = height
            faderLayer.frame = CGRect(x: tx, y: ty, width: tw, height: th)
            
            if type != .thumb {
                break
            }
            
            tw = height + thumbExtraSize
            ty = faderLayer.frame.origin.y - thumbExtraSize / 2
            tx = width - tw / 2
            if tx < 0 {
                tx = 0
            } else if tx > frame.size.width - tw {
                tx = frame.size.width - tw
            }
            
            thumbImageView.frame = CGRect(x: tx, y: ty, width: tw, height: tw)
            
        case (.fromMiddle, .vertical):
//            let toRange = 0...Double(frame.size.height)
            let toRange = getSlideFrameRange()
            let isUp = value > midValue
            var height = CGFloat(convert(
                value: value,
                inRange: minValue...maxValue,
                toRange: toRange))
            height = height.isNaN ? 0 : height
            
            let width = frame.size.width * trackRate
            
            //            print("----- width = \(width), height = \(height), value = \(value)")
            
            tx = (frame.size.width - width) / 2
            tw = width
            
            if isUp {
                ty = frame.size.height - height
                th = (frame.size.height / 2.0) - (frame.size.height - height)
                faderLayer.frame = CGRect(x: tx, y: ty, width: tw, height: th)
            } else {
                ty = frame.size.height / 2.0
                th = (frame.size.height - height) - (frame.size.height / 2.0)
                faderLayer.frame = CGRect(x: tx, y: ty, width: tw, height: th)
            }
            
            if type != .thumb {
                break
            }
            
            tw = width + thumbExtraSize
            tx -= thumbExtraSize / 2
            
            ty = (frame.size.height - height) - tw / 2
            if ty < 0 {
                ty = 0
            } else if ty > frame.size.height - tw {
                ty = frame.size.height - tw
            }
            
            thumbImageView.frame = CGRect(x: tx, y: ty, width: tw, height: tw)
            
        case (.fromMiddle, .horizontal):
//            let toRange = 0...Double(frame.size.width)
            let toRange = getSlideFrameRange()

            let isUp = value > midValue
            var width = CGFloat(convert(
                value: value,
                inRange: minValue...maxValue,
                toRange: toRange))
            width = width.isNaN ? 0 : width
            
            let height = frame.size.height * trackRate
            
            //            print("----- width = \(width), height = \(height)")
            
            if isUp {
                tx = frame.size.width / 2.0
                ty = (frame.size.height - height) / 2.0
                tw = width - (frame.size.width / 2.0)
                th = height
                faderLayer.frame = CGRect(x: tx, y: ty, width: tw,height: th)
            } else {
                tx = width
                ty = (frame.size.height - height) / 2.0
                tw = (frame.size.width / 2.0) - width
                th = height
                faderLayer.frame = CGRect(x: tx, y: ty, width: tw, height: th)
            }
            
            if type != .thumb {
                break
            }
            
            tw = height + thumbExtraSize
            ty = faderLayer.frame.origin.y - thumbExtraSize / 2
            tx = width - tw / 2
            if tx < 0 {
                tx = 0
            } else if tx > frame.size.width - tw {
                tx = frame.size.width - tw
            }
            
            thumbImageView.frame = CGRect(x: tx, y: ty, width: tw, height: tw)
        }
        
//        faderLayer.cornerRadius = trackCornerRadius
//        faderLayer.masksToBounds = true
        
        CATransaction.commit()
    }
    
    /// Sets the colors on enabled/disable/highlighted state changes.
    open func setupColors() {
        // Swift
        CATransaction.begin()
        CATransaction.setDisableActions(true) // 禁用所有隐式动画
        
        //        backgroundColor = isEnabled ?
        //        (isHighlighted ? (faderHighlightedBackgroundColor ?? faderEnabledBackgroundColor) : faderEnabledBackgroundColor) :
        //        faderDisabledBackgroundColor
        
        let bgColor = isEnabled ?
        (isHighlighted ? (faderHighlightedBackgroundColor ?? faderEnabledBackgroundColor) : faderEnabledBackgroundColor) :
        faderDisabledBackgroundColor
        trackBgLayer.backgroundColor = bgColor?.cgColor
        
        faderLayer.backgroundColor = isEnabled ?
        (isHighlighted ? (faderHighlightedForegroundColor?.cgColor ?? faderEnabledForegroundColor.cgColor) : faderEnabledForegroundColor.cgColor) :
        faderDisabledForegroundColor?.cgColor
        
        CATransaction.commit()
    }
    
    // MARK: Actions
    
    /// Sets the value of the fader.
    ///
    /// - Parameter pan: Pan gesture of the fader.
    @objc open func handleGestureRecognizer(gestureRecognizer: UIGestureRecognizer) {
        let preValue = value
        
        let touchPoint = gestureRecognizer.location(in: self)
        
        // Calculate value.
        switch direction {
        case .vertical:
//            let inRange = 0...frame.size.height
            let inRange = getSlideFrameRange()
            let touchValue = Double(convert(
                value: touchPoint.y,
                inRange: inRange,
                toRange: CGFloat(minValue)...CGFloat(maxValue)))
            
//                        print("touchPointy = \(touchPoint.y), touchValue = \(touchValue)")
            //            let newValue = maxValue - touchValue
            let newValue = maxValue + minValue - touchValue
            value = max(minValue, min(maxValue, newValue))
//            print("touchPointy = \(touchPoint.y), touchValue = \(touchValue), newValue = \(newValue), value = \(value)")
            
        case .horizontal:
//            let inRange = 0...frame.size.width
            let inRange = getSlideFrameRange()
            let newValue = Double(convert(
                value: touchPoint.x,
                inRange: inRange,
                toRange: CGFloat(minValue)...CGFloat(maxValue)))
            value = max(minValue, min(maxValue, newValue))
        }
        
        // Draw.
        if preValue != value {
            setNeedsLayout()
        }
        
        // Inform changes based on continuous behaviour of the control.
//        if continuous {
//            sendActions(for: .valueChanged)
//        } else {
//            if gestureRecognizer.state == .ended || gestureRecognizer.state == .cancelled {
//                sendActions(for: .valueChanged)
//            }
//        }

//        if gestureRecognizer.state == .ended {
//            sendActions(for: .editingDidEnd)
//        }
        
        if gestureRecognizer.state == .began {
            
        } else if gestureRecognizer.state == .ended || gestureRecognizer.state == .cancelled {
            sendActions(for: .editingDidEnd)
        } else {
            if value == preValue {
                return
            }
            sendActions(for: .valueChanged)
        }
    }
    
    // MARK: Utils
    func getSlideFrameRange() -> ClosedRange<Double> {
        switch direction {
        case .vertical:
            switch type {
            case .normal:
                return 0...Double(frame.size.height)
            case .thumb:
                let tSize = (frame.size.width * trackRate + thumbExtraSize) / 2
                let upperValue = max(frame.size.height - tSize, tSize)
                return tSize...upperValue
            }
            
        case .horizontal:
            switch type {
            case .normal:
                return 0...Double(frame.size.width)
            case .thumb:
                let tSize = (frame.size.height * trackRate + thumbExtraSize) / 2
                
                let upperValue = max(frame.size.width - tSize, tSize)
                return tSize...upperValue
            }
        }
    }
    
    
    /// Converts a value in a range to a value in another range.
    ///
    /// - Parameters:
    ///   - value: Value you want to convert.
    ///   - inRange: The range of the value you want to convert.
    ///   - toRange: The range you want your new value converted in.
    /// - Returns: Converted value in a new range.
    private func convert<T: FloatingPoint>(value: T, inRange: ClosedRange<T>, toRange: ClosedRange<T>) -> T {
        let oldRange = inRange.upperBound - inRange.lowerBound
        let newRange = toRange.upperBound - toRange.lowerBound
        return (((value - inRange.lowerBound) * newRange) / oldRange) + toRange.lowerBound
    }
}
